library(data.table)
data.table 1.15.2 using 80 threads (see ?getDTthreads).  Latest news: r-datatable.com
library(readxl)
library(ggplot2)
library(gprofiler2)
library(glmnet)
Loading required package: Matrix
Loaded glmnet 4.1-8
library(ComplexHeatmap)
Loading required package: grid
========================================
ComplexHeatmap version 2.14.0
Bioconductor page: http://bioconductor.org/packages/ComplexHeatmap/
Github page: https://github.com/jokergoo/ComplexHeatmap
Documentation: http://jokergoo.github.io/ComplexHeatmap-reference

If you use it in published research, please cite either one:
- Gu, Z. Complex Heatmap Visualization. iMeta 2022.
- Gu, Z. Complex heatmaps reveal patterns and correlations in multidimensional 
    genomic data. Bioinformatics 2016.


The new InteractiveComplexHeatmap package can directly export static 
complex heatmaps into an interactive Shiny app with zero effort. Have a try!

This message can be suppressed by:
  suppressPackageStartupMessages(library(ComplexHeatmap))
========================================
library(pROC)
Type 'citation("pROC")' for a citation.

Attaching package: ‘pROC’

The following objects are masked from ‘package:stats’:

    cov, smooth, var
library(caret)
Loading required package: lattice
library(mixOmics)
Loading required package: MASS

Loaded mixOmics 6.22.0
Thank you for using mixOmics!
Tutorials: http://mixomics.org
Bookdown vignette: https://mixomicsteam.github.io/Bookdown
Questions, issues: Follow the prompts at http://mixomics.org/contact-us
Cite us:  citation('mixOmics')


Attaching package: ‘mixOmics’

The following objects are masked from ‘package:caret’:

    nearZeroVar, plsda, splsda
library(infotheo)
library(circlize)
========================================
circlize version 0.4.16
CRAN page: https://cran.r-project.org/package=circlize
Github page: https://github.com/jokergoo/circlize
Documentation: https://jokergoo.github.io/circlize_book/book/

If you use it in published research, please cite:
Gu, Z. circlize implements and enhances circular visualization
  in R. Bioinformatics 2014.

This message can be suppressed by:
  suppressPackageStartupMessages(library(circlize))
========================================
library(grDevices)
set.seed(101)

Data preprocessing

Read data

# read meta data
meta <- as.data.table(readxl::read_excel("data/transcritome_patients_translated.xlsx"))

meta_key <- "CEL_FILE"
setkeyv(meta, meta_key)                             # set key
stopifnot(!any( duplicated(meta[,..meta_key]) ))    # check for duplicate rows
stopifnot(!any( duplicated(colnames(meta)) ))       # check for duplicate columns

# read Feuil1 
feuil1 <- as.data.table(readxl::read_excel("data/transcriptome_expression_matrix.xlsx",sheet="Feuil1"))
New names:
# read expression data
expr <- as.data.table(readxl::read_excel("data/transcriptome_expression_matrix.xlsx"))
New names:
expr_key <- "gene"
colnames(expr)[1] <- expr_key
setkeyv(expr, expr_key)                             # set key
stopifnot(!any( duplicated(expr[,..expr_key]) ))    # check for duplicate rows
stopifnot(!any( duplicated(colnames(expr)) ))       # check for duplicate columns

expr <- expr[, c(key(expr), meta$CEL_FILE), with=F]
stopifnot(all(colnames(expr)[-1]==meta[,CEL_FILE]))
expr <- as.matrix(expr, rownames="gene")

meta_M0 <- meta[LOCATION=="M0"]
meta_MI <- meta[LOCATION=="MI"]
meta_M6 <- meta[LOCATION=="M6"]
meta_M0M6 <- meta[LOCATION=="M6"|LOCATION=="M0"]
meta_M0MI <- meta[LOCATION=="M0"|LOCATION=="MI"]
meta_M0MIC <- meta[LOCATION=="M0"|LOCATION=="MI"|LOCATION=="Ctrl"]

# read mouse signature
signature_dt <- fread("data/signature.csv")
signature_mouse <- signature_dt$gene
setkeyv(signature_dt, "gene")

# read all mouse genes measured with nanostring 
measured_genes_mouse <- fread("data/All samples_NormalizedData.csv")[[1]]

# check that all signature genes are part of the measured genes
stopifnot(all(signature_mouse %in% measured_genes_mouse))

Ortholog mapping

# map to human orthologs
ortholog_mapping <- as.data.table(gprofiler2::gorth(measured_genes_mouse, source_organism = "mmusculus", target_organism = "hsapiens", filter_na = F))
stopifnot(all(measured_genes_mouse %in% ortholog_mapping$input))

# mark signature genes and hits in the human expr data
ortholog_mapping$in_expr <- ortholog_mapping$ortholog_name %in% rownames(expr)
ortholog_mapping$in_signature <- ortholog_mapping$input %in% signature_mouse 
ortholog_mapping[in_signature==T,mouse:=signature_dt[input,pattern]]

# count number of hits in the human expr data per mouse gene
n_in_expr <- ortholog_mapping[,.("n_hits" = sum(in_expr)),by=input]

# print number of hits in expr$gene per measured mouse gene
table(n_in_expr$n_hits)

  0   1   2   3   6   7 
 48 708   8   2   1   1 
# select only mouse genes with orthologs uniquely mapped to the human expression data
unique_ortholog_mapping <- ortholog_mapping[input %in% n_in_expr[n_hits==1,input] & in_expr]
# unmapped signature genes
print(paste0(nrow(unique_ortholog_mapping)," out of ",length(measured_genes_mouse), " measured mouse genes were mapped to the human expression genes"))
[1] "708 out of 768 measured mouse genes were mapped to the human expression genes"
ortholog_mapping[!input %in% unique_ortholog_mapping$input & in_signature]

Data checks

Expression sum per sample

plot_data <- data.table(sample=colnames(expr))
plot_data[,colSum:=colSums(expr)]
plot_data[,RECURRENCE:=meta$RECURRENCE]
plot_data[,LOCATION:=meta$LOCATION]
plot_data[,GENDER:=meta$GENDER]

ggplot(plot_data,aes(x=colSum,fill=RECURRENCE)) +
  geom_density(alpha=0.5)


ggplot(plot_data,aes(x=colSum,fill=LOCATION)) +
  geom_density(alpha=0.5)


ggplot(plot_data,aes(x=colSum,fill=GENDER)) +
  geom_density(alpha=0.5)

PCA

high_var_genes <- sort(apply(expr[,meta$CEL_FILE], 1, sd), decreasing = T)[1:10000]
high_var_expr <- t(expr[names(high_var_genes),meta$CEL_FILE])

high_var_pca <- pca(high_var_expr, ncomp = 3, scale = T)
plotIndiv(high_var_pca, group = meta$LOCATION, ind.names = FALSE,
          legend = TRUE, title="PCA - high variance genes", ellipse = T)


signature_pca <- pca(t(expr[unique_ortholog_mapping[in_expr==T & in_signature, ortholog_name], meta$CEL_FILE]), ncomp = 3, scale = TRUE)
plotIndiv(signature_pca, group = meta$LOCATION, ind.names = FALSE,
          legend = TRUE, title="PCA - signature genes", ellipse = T)

Meta checks

table(meta$RECURRENCE, meta$LOCATION)
      
       Ctrl  M0  M6  MI
  Ctrl   25   0   0   0
  NR      0  57  36  43
  R       0 139  85 104
table(meta$LOCATION)

Ctrl   M0   M6   MI 
  25  196  121  147 

Ctrl (25) -> Ctrl (25) M0 (196) -> M0I (200) MI (147) -> M0M (149) M6 (121) -> M6 (122) Why do the numbers from publication and meta sheet not match?

What is CENTRE? What are stenose, fistule, inflammatoire, Stoma? What is Postoperative anti-TNF?

Why do RutgeertRec and RECURRENCE not match 1 to 1?

table(meta$RutgeertRec, meta$RECURRENCE)
      
       Ctrl  NR   R
  Ctrl   25   0   0
  Rec     0   0 326
  Rem     0 136   2

Signature heatmap

custom_heatmap <- function(expr, meta, genes, column_names = F, scale = T, ...){
  
  if(is.null(meta)){
    samples <- colnames(expr)
      column_ha = NULL
  }else{
    meta <- as.data.frame(meta, row.names = meta[[1]])[,-1,drop=F]
    samples <- rownames(meta)
    column_ha = HeatmapAnnotation(df=meta)
  }
  
  genes <- as.data.frame(genes, row.names = genes[[1]])[,-1,drop=F]
  gene_names <- rownames(genes)
  row_ha = rowAnnotation(
    df=genes, 
    annotation_name_side="top",
    #annotation_label=("\u0394/\u0394IEC"),
    col = list(mouse= c("up" = "#E8E700", "down" = "#0092F4"))
  )
  
  if(scale){
    expr <- t(apply(expr[gene_names,samples],1,scale))
    colnames(expr) <- samples
  }else{
    expr <- expr[gene_names,samples]
  }
  
  
  Heatmap(expr,
        show_column_names = column_names, 
        top_annotation = column_ha,
        name = "Expression",
        row_names_gp = gpar(fontsize = 10),
        left_annotation = row_ha,
        column_title_side = "top",
        ...
        )
}
custom_heatmap <- function(expr, meta, genes, column_names = F, scale = T, ...){
  
  if(is.null(meta)){
    samples <- colnames(expr)
      column_ha = NULL
  }else{
    meta <- as.data.frame(meta, row.names = meta[[1]])[,-1,drop=F]
    samples <- rownames(meta)
    column_ha = HeatmapAnnotation(df=meta)
  }
  
  genes <- as.data.frame(genes, row.names = genes[[1]])[,-1,drop=F]
  gene_names <- rownames(genes)
  row_ha = rowAnnotation(
    df=genes, 
    annotation_name_side="top",
    #annotation_label=("\u0394/\u0394IEC"),
    col = list(mouse= c("up" = "#E8E700", "down" = "#0092F4"))
  )
  
  if(scale){
    expr <- t(apply(expr[gene_names,samples],1,scale))
    colnames(expr) <- samples
  }else{
    expr <- expr[gene_names,samples]
  }
  
  
  Heatmap(expr,
        show_column_names = column_names, 
        top_annotation = column_ha,
        name = "Expression",
        row_names_gp = gpar(fontsize = 10),
        left_annotation = row_ha,
        column_title_side = "top",
        ...
        )
}

custom_heatmap(
  expr, 
  meta[LOCATION=="M0",.(CEL_FILE,RECURRENCE,LOCATION,inflammatoire,Smoker,Granuloma,Rutgeert2)],
  unique_ortholog_mapping[in_expr==T&in_signature==T,.(ortholog_name,mouse)],
  cluster_columns = T)


custom_heatmap(
  expr, 
  meta[LOCATION=="M6",.(CEL_FILE,RECURRENCE,LOCATION,Reecal_Rut=as.numeric(Reeval_Rut))],
  unique_ortholog_mapping[in_expr==T&in_signature==T,.(ortholog_name,mouse)],
  cluster_columns = T)


custom_heatmap(
  expr, 
  meta[LOCATION=="M6",.(CEL_FILE,RECURRENCE,LOCATION)][order(RECURRENCE)],
  unique_ortholog_mapping[in_expr==T&in_signature==T,.(ortholog_name,mouse)],
  cluster_columns = F)


custom_heatmap(
  expr, 
  meta[LOCATION=="M0"|LOCATION=="MI",.(CEL_FILE,LOCATION)],
  unique_ortholog_mapping[in_expr==T&in_signature==T,.(ortholog_name,mouse)],
  cluster_columns = T)


custom_heatmap(
  expr, 
  meta[LOCATION=="M0"|LOCATION=="MI",.(CEL_FILE,LOCATION)][order(LOCATION)],
  unique_ortholog_mapping[in_expr==T&in_signature==T,.(ortholog_name,mouse)],
  cluster_columns = F)


custom_heatmap(
  expr, 
  meta[LOCATION=="M0"|LOCATION=="MI",.(CEL_FILE,LOCATION)][order(LOCATION)],
  unique_ortholog_mapping[in_expr==T&in_signature==T,.(ortholog_name,mouse)][order(mouse)],
  cluster_columns = F, cluster_rows = F)

NA
NA

Location summarized heatmap

# MI, M0, Ctrl
expr_scaled <- apply(expr[unique_ortholog_mapping[in_expr==T&in_signature==T,ortholog_name],meta_M0MIC$CEL_FILE] ,1,scale)
rownames(expr_scaled) <- meta_M0MIC$CEL_FILE

summarized_expr <- sapply(unique(meta_M0MIC$LOCATION), function(location){
  colMeans(expr_scaled[meta_M0MIC[LOCATION==location,CEL_FILE],])
})

# mutual information
concordance <- data.frame(
  mouse = unique_ortholog_mapping[in_signature==T,mouse],
  human = ifelse(sign(summarized_expr[unique_ortholog_mapping[in_signature==T,ortholog_name],"M0"])>0, "up", "down")
  )
concordance$concordance <- concordance$mouse == concordance$human
mi <- mutinformation(concordance$mouse,concordance$human)
random_mi <- sapply(1:1000, function(i){mutinformation(sample(concordance$mouse),concordance$human)})
mi_pval <- (sum(random_mi>mi) + 1)/(length(random_mi)+1)
paste("concordant genes:",sum(concordance$concordance),"mutual information:",mi,"p-value",mi_pval)
[1] "concordant genes: 23 mutual information: 0.0923613499679372 p-value 0.00699300699300699"
custom_heatmap(summarized_expr, NULL, unique_ortholog_mapping[in_signature==T,.(ortholog_name,mouse,concordance=concordance$concordance)] ,column_names=T, scale = F)

custom_heatmap(summarized_expr, NULL, unique_ortholog_mapping[in_signature==T,.(ortholog_name,mouse,concordance=concordance$concordance)][order(mouse)],cluster_rows = F, column_names=T, scale = F)



# MI, M0
expr_scaled <- apply(expr[unique_ortholog_mapping[in_expr==T&in_signature==T,ortholog_name],meta_M0MI$CEL_FILE] ,1,scale)
rownames(expr_scaled) <- meta_M0MI$CEL_FILE

summarized_expr <- sapply(unique(meta_M0MI$LOCATION), function(location){
  colMeans(expr_scaled[meta_M0MI[LOCATION==location,CEL_FILE],])
})

# mutual information
concordance <- data.frame(
  mouse = unique_ortholog_mapping[in_signature==T,mouse],
  human = ifelse(sign(summarized_expr[unique_ortholog_mapping[in_signature==T,ortholog_name],"M0"])>0, "up", "down")
  )
concordance$concordance <- concordance$mouse == concordance$human
mi <- mutinformation(concordance$mouse,concordance$human)
random_mi <- sapply(1:1000, function(i){mutinformation(sample(concordance$mouse),concordance$human)})
mi_pval <- (sum(random_mi>mi) + 1)/(length(random_mi)+1)
paste("concordant genes:",sum(concordance$concordance),"mutual information:",mi,"p-value",mi_pval)
[1] "concordant genes: 24 mutual information: 0.129536460802155 p-value 0.00599400599400599"
custom_heatmap(summarized_expr, NULL, unique_ortholog_mapping[in_signature==T,.(ortholog_name,mouse,concordance=concordance$concordance)] ,column_names=T, scale = F)

custom_heatmap(summarized_expr, NULL, unique_ortholog_mapping[in_signature==T,.(ortholog_name,mouse,concordance=concordance$concordance)][order(mouse)],cluster_rows = F, column_names=T, scale = F)

For paper


# scale expression data
expr_scaled <- apply(expr[unique_ortholog_mapping[in_signature==T,ortholog_name],meta_M0MI$CEL_FILE] ,1,scale)
rownames(expr_scaled) <- meta_M0MI$CEL_FILE

# mean per location
summarized_expr <- sapply(unique(c("MI","M0")), function(location){
  colMeans(expr_scaled[meta_M0MI[LOCATION==location,CEL_FILE],])
})

# concordance data
concordance <- data.frame(
  mouse = unique_ortholog_mapping[in_signature==T,mouse],
  human = ifelse(sign(summarized_expr[unique_ortholog_mapping[in_signature==T,ortholog_name],"M0"])>0, "up", "down")
  )
concordance$concordance <- concordance$mouse == concordance$human
mi <- mutinformation(concordance$mouse,concordance$human)
set.seed(0)
random_mi <- sapply(1:10000, function(i){mutinformation(sample(concordance$mouse),concordance$human)})
mi_pval <- (sum(random_mi>mi) + 1)/(length(random_mi)+1)
paste("concordant genes:",sum(concordance$concordance),"mutual information:",mi,"p-value",mi_pval)
[1] "concordant genes: 24 mutual information: 0.129536460802155 p-value 0.004999500049995"
concordance$Mice <- ifelse(concordance$mouse=="up", "up in \u0394/\u0394IEC", "down in \u0394/\u0394IEC")
grDevices::cairo_pdf("heatmap.pdf", width = 4, height = 7,)
# row annotation
row_ha = rowAnnotation(
  Mice=concordance$Mice, 
  annotation_name_side="top",
  annotation_name_rot=0,
  col = list(Mice= c("up in \u0394/\u0394IEC" = "#E8E700", "down in \u0394/\u0394IEC" = "#0092F4"))
)

col_fun = colorRamp2(c(-0.5, 0, 0.5), c("blue", "white", "red"))

concordance$col <- "grey"
concordance[concordance$concordance & concordance$mouse=="up",]$col <- "red"
concordance[concordance$concordance & concordance$mouse=="down",]$col <- "blue"

Heatmap(summarized_expr,
  name = "Expression",
  row_names_gp = gpar(fontsize = 9, col = concordance$col),
  left_annotation = row_ha,
  column_title_side = "top",
  cluster_columns = F,
  column_labels = c("M0M","M0I"),
  column_names_side = "top",
  column_names_rot = 0,
  column_names_centered = T,
  col = col_fun,
  width = unit(3, "cm"),
  )
dev.off()
null device 
          1 
  
custom_heatmap(summarized_expr, NULL, unique_ortholog_mapping[in_signature==T,.(ortholog_name,mouse,concordance=concordance$concordance)] ,column_names=T, scale = F)

PLS-DA Analysis

high_var_genes <- sort(apply(expr[,meta_M0$CEL_FILE], 1, sd), decreasing = T)[1:5000]
pls_da_expr <- t(expr[names(high_var_genes),meta_M0$CEL_FILE])
#pls_da_expr <- 2 ^ pls_da_expr

pca.expr <- pca(pls_da_expr, ncomp = 3, scale = TRUE)
plotIndiv(pca.expr, group = meta_M0$RECURRENCE, ind.names = FALSE,
          legend = TRUE, 
          title = 'PCA comp 1 - 2')


plsda.expr <- plsda(pls_da_expr, meta_M0$RECURRENCE, ncomp = 10)

perf.plsda.expr <- perf(plsda.expr, validation = 'Mfold', folds = 3, 
                  progressBar = FALSE,
                  nrepeat = 10)         

plot(perf.plsda.expr, sd = TRUE, legend.position = 'horizontal')

M0 vs MI prediction

logistic_glmnet_loc <- function(meta, expr, signature){
  
  x <- t(expr[signature, meta$CEL_FILE])
  y <- meta$LOCATION
  
  fit <- glmnet(x, y, family = "binomial")
  plot(fit, label = T)
  cvfit <- cv.glmnet(x, y, family = "binomial")
  plot(cvfit)
  print(cvfit)
  print(coef(cvfit, s = "lambda.1se"))
}

logistic_glm_loc <- function(meta, expr, signature, roc=F, summary=F, performance=F){
  
  x <- t(expr[signature, meta$CEL_FILE])
  y <- meta$LOCATION
  
  data <- as.data.frame(x)
  data$LOCATION <- 0
  data$LOCATION[y=="M0"] <- 1
  
  glm_model <- glm(LOCATION ~.,family = "binomial", data)
  
  # prediction
  model_prob = predict(glm_model, type = "response")
  model_pred = ifelse(model_prob > 0.5, "M0", "MI")
  train_tab = table(predicted = model_pred, actual = y)
  train_con_mat = confusionMatrix(train_tab)
  
  if(summary){
    print(summary(glm_model))
  }
  
  if(roc){
    roc(y ~ model_prob, plot = TRUE, print.auc = TRUE)
  }

  if(performance){
    print(train_con_mat)
  }
  
  train_con_mat$overall["Accuracy"]
}
signature_human <- unique_ortholog_mapping[in_signature==T,ortholog_name]
logistic_glmnet_loc(meta_M0MI, expr, signature_human)


Call:  cv.glmnet(x = x, y = y, family = "binomial") 

Measure: Binomial Deviance 

     Lambda Index Measure      SE Nonzero
min 0.00653    39   1.085 0.03910      22
1se 0.05055    17   1.121 0.03892       7
33 x 1 sparse Matrix of class "dgCMatrix"
                     s1
(Intercept)  2.55205119
ALDOB        .         
APOA1        0.14498197
APOB         .         
APOC3        .         
ARG1         .         
ASNS         .         
CACNA1A      .         
CD274        .         
CPS1         .         
CREB3L3      0.02933740
CXCL9        .         
DMGDH        .         
DUOX2        .         
FAHD1        .         
ICOS         .         
IDO1         .         
INMT        -0.10196269
KYAT3        .         
NOS2         .         
NOX1         .         
OTC          0.07169543
PCK1         .         
PDK4         .         
PHGDH        .         
PSAT1        .         
PTK6         .         
RIMKLA       .         
SLC7A11     -0.21170216
SPIB         .         
STAT1       -0.27217305
TLR4        -0.05028450
TNF          .         

Logistic regression with full signature

# Analysis with glm
accuracy <- logistic_glm_loc(meta_M0MI, expr, signature_human, T, T, T)

Call:
glm(formula = LOCATION ~ ., family = "binomial", data = data)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.6509  -0.6866   0.1288   0.7103   2.5686  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept) 21.87211   14.56681   1.502  0.13323    
ALDOB       -2.40915    0.78284  -3.077  0.00209 ** 
APOA1       -0.39068    0.27320  -1.430  0.15271    
APOB         0.20633    0.35331   0.584  0.55921    
APOC3        0.36578    0.26479   1.381  0.16715    
ARG1        -0.66917    0.86300  -0.775  0.43811    
ASNS        -0.04427    0.47465  -0.093  0.92568    
CACNA1A      0.94422    0.51347   1.839  0.06593 .  
CD274       -0.26257    0.32521  -0.807  0.41945    
CPS1         1.70922    0.42632   4.009 6.09e-05 ***
CREB3L3     -0.21153    0.27957  -0.757  0.44927    
CXCL9       -0.28598    0.22484  -1.272  0.20339    
DMGDH       -0.49668    0.90277  -0.550  0.58220    
DUOX2       -0.07147    0.12958  -0.552  0.58128    
FAHD1       -0.03254    0.53800  -0.060  0.95177    
ICOS        -0.15047    0.40333  -0.373  0.70910    
IDO1         0.29994    0.31233   0.960  0.33688    
INMT         0.25605    0.33474   0.765  0.44432    
KYAT3       -0.60916    0.73320  -0.831  0.40607    
NOS2         0.20450    0.28283   0.723  0.46965    
NOX1        -0.88635    0.44853  -1.976  0.04814 *  
OTC         -1.03341    0.42154  -2.452  0.01423 *  
PCK1         0.33806    0.18987   1.780  0.07500 .  
PDK4        -0.12923    0.18824  -0.687  0.49238    
PHGDH       -0.59088    0.62564  -0.944  0.34495    
PSAT1       -0.43509    0.36122  -1.205  0.22839    
PTK6         0.41397    0.49678   0.833  0.40467    
RIMKLA      -0.18986    0.58764  -0.323  0.74663    
SLC7A11      0.60062    0.27040   2.221  0.02634 *  
SPIB        -0.18824    0.26705  -0.705  0.48086    
STAT1        0.68441    0.50918   1.344  0.17890    
TLR4         0.68613    0.36408   1.885  0.05949 .  
TNF         -0.24646    0.42423  -0.581  0.56128    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 468.47  on 342  degrees of freedom
Residual deviance: 297.93  on 310  degrees of freedom
AIC: 363.93

Number of Fisher Scoring iterations: 7
Setting levels: control = M0, case = MI
Setting direction: controls > cases
Confusion Matrix and Statistics

         actual
predicted  M0  MI
       M0 164  35
       MI  32 112
                                          
               Accuracy : 0.8047          
                 95% CI : (0.7587, 0.8453)
    No Information Rate : 0.5714          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.6002          
                                          
 Mcnemar's Test P-Value : 0.807           
                                          
            Sensitivity : 0.8367          
            Specificity : 0.7619          
         Pos Pred Value : 0.8241          
         Neg Pred Value : 0.7778          
             Prevalence : 0.5714          
         Detection Rate : 0.4781          
   Detection Prevalence : 0.5802          
      Balanced Accuracy : 0.7993          
                                          
       'Positive' Class : M0              
                                          

Logistic regression with random signatures


random_signatures <- lapply(1:1000,function(i){
  sample(unique_ortholog_mapping$ortholog_name,length(signature_human))
})

random_accuracy <- sapply(random_signatures, function(random_signature){
  logistic_glm_loc(meta_M0MI, expr, random_signature)
})

ggplot(data.frame(random_accuracy=random_accuracy), aes(x=random_accuracy)) +
  geom_histogram(bins=30) +
  geom_vline(xintercept=accuracy, color="red")


(sum(random_accuracy>accuracy)+1)/(length(random_signatures)+1)
[1] 0.2217782

Recurrence prediction

logistic_glmnet <- function(meta, expr, signature){
  
  x <- t(expr[signature, meta$CEL_FILE])
  y <- meta$RECURRENCE
  
  fit <- glmnet(x, y, family = "binomial")
  plot(fit, label = T)
  cvfit <- cv.glmnet(x, y, family = "binomial", type.measure = "class")
  plot(cvfit)
  print(cvfit)
}

logistic_glm <- function(meta, expr, signature, roc=F, summary=F, performance=F){
  
  x <- t(expr[signature, meta$CEL_FILE])
  y <- meta$RECURRENCE
  
  data <- as.data.frame(x)
  data$RECURRENCE <- 0
  data$RECURRENCE[y=="R"] <- 1
  
  glm_model <- glm(RECURRENCE ~.,family = "binomial", data)
  
  # prediction
  model_prob = predict(glm_model, type = "response")
  model_pred = ifelse(model_prob > 0.5, "R", "NR")
  train_tab = table(predicted = model_pred, actual = y)
  train_con_mat = confusionMatrix(train_tab)
  
  if(summary){
    print(summary(glm_model))
  }
  
  if(roc){
    roc(y ~ model_prob, plot = TRUE, print.auc = TRUE)
  }

  if(performance){
    print(train_con_mat)
  }
  
  train_con_mat$overall["Accuracy"]
}
signature_human <- unique_ortholog_mapping[in_signature==T,ortholog_name]

M0

Feature selection with logistic glmnet

logistic_glmnet(meta_M0, expr, signature_human)


Call:  cv.glmnet(x = x, y = y, type.measure = "class", family = "binomial") 

Measure: Misclassification Error 

     Lambda Index Measure     SE Nonzero
min 0.06086     1  0.2908 0.0275       0
1se 0.06086     1  0.2908 0.0275       0

Logistic regression with full signature

# Analysis with glm
accuracy <- logistic_glm(meta_M0, expr, signature_human, T, T, T)

Call:
glm(formula = RECURRENCE ~ ., family = "binomial", data = data)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.6967  -0.8290   0.5509   0.8016   1.8942  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)  
(Intercept) -15.06772   15.79400  -0.954   0.3401  
ALDOB        -0.92023    0.41840  -2.199   0.0278 *
APOA1         0.10998    0.27791   0.396   0.6923  
APOB          0.52203    0.30590   1.707   0.0879 .
APOC3        -0.27377    0.29954  -0.914   0.3607  
ARG1          0.14516    1.07260   0.135   0.8923  
ASNS         -0.42582    0.53167  -0.801   0.4232  
CACNA1A       0.16560    0.62152   0.266   0.7899  
CD274         0.07812    0.37852   0.206   0.8365  
CPS1          0.21568    0.37820   0.570   0.5685  
CREB3L3       0.05876    0.34063   0.173   0.8630  
CXCL9         0.25207    0.28752   0.877   0.3806  
DMGDH        -1.29012    0.89202  -1.446   0.1481  
DUOX2        -0.05173    0.18021  -0.287   0.7741  
FAHD1         0.41210    0.68509   0.602   0.5475  
ICOS         -0.50734    0.48449  -1.047   0.2950  
IDO1         -0.24146    0.40907  -0.590   0.5550  
INMT          0.74965    0.38851   1.930   0.0537 .
KYAT3         1.29103    0.80823   1.597   0.1102  
NOS2         -0.06091    0.35451  -0.172   0.8636  
NOX1          0.52374    0.55059   0.951   0.3415  
OTC          -0.03520    0.42550  -0.083   0.9341  
PCK1          0.16418    0.17516   0.937   0.3486  
PDK4         -0.13744    0.24200  -0.568   0.5701  
PHGDH        -0.16188    0.64987  -0.249   0.8033  
PSAT1        -0.19922    0.44249  -0.450   0.6526  
PTK6         -0.55283    0.65167  -0.848   0.3963  
RIMKLA        0.94240    0.71040   1.327   0.1847  
SLC7A11       0.42755    0.28598   1.495   0.1349  
SPIB          0.20429    0.31458   0.649   0.5161  
STAT1         0.80068    0.72341   1.107   0.2684  
TLR4         -0.34050    0.39892  -0.854   0.3934  
TNF          -0.34636    0.39681  -0.873   0.3827  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 236.33  on 195  degrees of freedom
Residual deviance: 200.82  on 163  degrees of freedom
AIC: 266.82

Number of Fisher Scoring iterations: 5
Setting levels: control = NR, case = R
Setting direction: controls < cases
Confusion Matrix and Statistics

         actual
predicted  NR   R
       NR  19   9
       R   38 130
                                          
               Accuracy : 0.7602          
                 95% CI : (0.6942, 0.8182)
    No Information Rate : 0.7092          
    P-Value [Acc > NIR] : 0.06559         
                                          
                  Kappa : 0.316           
                                          
 Mcnemar's Test P-Value : 4.423e-05       
                                          
            Sensitivity : 0.33333         
            Specificity : 0.93525         
         Pos Pred Value : 0.67857         
         Neg Pred Value : 0.77381         
             Prevalence : 0.29082         
         Detection Rate : 0.09694         
   Detection Prevalence : 0.14286         
      Balanced Accuracy : 0.63429         
                                          
       'Positive' Class : NR              
                                          

Logistic regression with random signatures


random_signatures <- lapply(1:1000,function(i){
  sample(unique_ortholog_mapping$ortholog_name,length(signature_human))
})

random_accuracy <- sapply(random_signatures, function(random_signature){
  logistic_glm(meta_M0, expr, random_signature)
})

ggplot(data.frame(random_accuracy=random_accuracy), aes(x=random_accuracy)) +
  geom_histogram(bins=30) +
  geom_vline(xintercept=accuracy, color="red")

M6

Feature selection with logistic glmnet

logistic_glmnet(meta_M6, expr, signature_human)


Call:  cv.glmnet(x = x, y = y, type.measure = "class", family = "binomial") 

Measure: Misclassification Error 

     Lambda Index Measure      SE Nonzero
min 0.07089     8  0.2893 0.03628       5
1se 0.13596     1  0.2975 0.03363       0

Logistic regression with full signature

# Analysis with glm
accuracy <- logistic_glm(meta_M6, expr, signature_human, T, T, T)

Call:
glm(formula = RECURRENCE ~ ., family = "binomial", data = data)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.3392  -0.5410   0.2409   0.6472   1.6068  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)    
(Intercept) -11.93445   27.08750  -0.441  0.65951    
ALDOB         0.95027    1.21594   0.782  0.43450    
APOA1        -1.62620    1.12759  -1.442  0.14925    
APOB          0.95201    1.37919   0.690  0.49003    
APOC3         0.27513    0.96583   0.285  0.77575    
ARG1         -0.62188    2.15069  -0.289  0.77246    
ASNS         -1.23129    0.95416  -1.290  0.19689    
CACNA1A      -2.16046    1.20953  -1.786  0.07407 .  
CD274         0.99056    0.86740   1.142  0.25346    
CPS1         -0.13152    1.07094  -0.123  0.90226    
CREB3L3       0.47578    0.61427   0.775  0.43861    
CXCL9         0.18221    0.49859   0.365  0.71477    
DMGDH        -1.96184    2.60050  -0.754  0.45060    
DUOX2         0.28120    0.28756   0.978  0.32814    
FAHD1         3.23421    1.45507   2.223  0.02623 *  
ICOS          0.47828    0.98646   0.485  0.62779    
IDO1         -0.03772    0.79989  -0.047  0.96239    
INMT         -0.56813    1.15972  -0.490  0.62421    
KYAT3         0.55285    1.49817   0.369  0.71212    
NOS2         -1.08783    0.58724  -1.852  0.06396 .  
NOX1          0.50637    0.96109   0.527  0.59828    
OTC          -1.05608    1.05991  -0.996  0.31906    
PCK1          0.42407    0.54764   0.774  0.43872    
PDK4         -1.22094    0.63454  -1.924  0.05434 .  
PHGDH         5.37410    1.49335   3.599  0.00032 ***
PSAT1        -0.10896    0.70550  -0.154  0.87726    
PTK6         -2.29695    1.14187  -2.012  0.04426 *  
RIMKLA       -0.63140    1.50560  -0.419  0.67495    
SLC7A11      -0.21761    0.75281  -0.289  0.77253    
SPIB         -0.68279    0.81850  -0.834  0.40417    
STAT1        -0.35248    0.99028  -0.356  0.72189    
TLR4         -0.15202    0.82122  -0.185  0.85314    
TNF           2.17205    1.21091   1.794  0.07286 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 147.317  on 120  degrees of freedom
Residual deviance:  94.893  on  88  degrees of freedom
AIC: 160.89

Number of Fisher Scoring iterations: 6
Setting levels: control = NR, case = R
Setting direction: controls < cases
Confusion Matrix and Statistics

         actual
predicted NR  R
       NR 23 10
       R  13 75
                                          
               Accuracy : 0.8099          
                 95% CI : (0.7286, 0.8755)
    No Information Rate : 0.7025          
    P-Value [Acc > NIR] : 0.005027        
                                          
                  Kappa : 0.5341          
                                          
 Mcnemar's Test P-Value : 0.676657        
                                          
            Sensitivity : 0.6389          
            Specificity : 0.8824          
         Pos Pred Value : 0.6970          
         Neg Pred Value : 0.8523          
             Prevalence : 0.2975          
         Detection Rate : 0.1901          
   Detection Prevalence : 0.2727          
      Balanced Accuracy : 0.7606          
                                          
       'Positive' Class : NR              
                                          

Logistic regression with random signatures


random_signatures <- lapply(1:1000,function(i){
  sample(unique_ortholog_mapping$ortholog_name,length(signature_human))
})

random_accuracy <- sapply(random_signatures, function(random_signature){
  logistic_glm(meta_M6, expr, random_signature)
})
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: fitted probabilities numerically 0 or 1 occurred
ggplot(data.frame(random_accuracy=random_accuracy), aes(x=random_accuracy)) +
  geom_histogram(bins=30) +
  geom_vline(xintercept=accuracy, color="red")

MI

Feature selection with logistic glmnet

logistic_glmnet(meta_MI, expr, signature_human)


Call:  cv.glmnet(x = x, y = y, type.measure = "class", family = "binomial") 

Measure: Misclassification Error 

     Lambda Index Measure      SE Nonzero
min 0.05889     1  0.2925 0.03973       0
1se 0.05889     1  0.2925 0.03973       0

Logistic regression with full signature

# Analysis with glm
accuracy <- logistic_glm(meta_MI, expr, signature_human, T, T, T)

Call:
glm(formula = RECURRENCE ~ ., family = "binomial", data = data)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.3934  -0.7820   0.4690   0.7313   2.2220  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)  
(Intercept) -4.17530   24.79441  -0.168   0.8663  
ALDOB        2.26021    1.58793   1.423   0.1546  
APOA1        1.00416    0.53227   1.887   0.0592 .
APOB        -2.01022    1.00052  -2.009   0.0445 *
APOC3       -0.30466    0.52124  -0.584   0.5589  
ARG1        -1.85550    1.28265  -1.447   0.1480  
ASNS         0.22660    0.75871   0.299   0.7652  
CACNA1A      0.01282    0.77252   0.017   0.9868  
CD274        0.56382    0.57507   0.980   0.3269  
CPS1         0.53332    0.65796   0.811   0.4176  
CREB3L3     -0.54022    0.47594  -1.135   0.2564  
CXCL9       -0.30102    0.37615  -0.800   0.4236  
DMGDH        0.41788    1.88106   0.222   0.8242  
DUOX2        0.10864    0.19018   0.571   0.5678  
FAHD1        0.36869    0.80406   0.459   0.6466  
ICOS        -1.39655    0.63234  -2.209   0.0272 *
IDO1         0.78642    0.52369   1.502   0.1332  
INMT         0.43336    0.54341   0.797   0.4252  
KYAT3        0.99032    1.16555   0.850   0.3955  
NOS2         0.52479    0.46142   1.137   0.2554  
NOX1         0.27130    0.85806   0.316   0.7519  
OTC         -0.83141    0.74064  -1.123   0.2616  
PCK1        -0.21972    0.48399  -0.454   0.6498  
PDK4        -0.11671    0.32671  -0.357   0.7209  
PHGDH       -0.37393    1.09492  -0.342   0.7327  
PSAT1       -0.36391    0.58402  -0.623   0.5332  
PTK6         0.16596    0.76056   0.218   0.8273  
RIMKLA      -0.51355    1.12269  -0.457   0.6474  
SLC7A11     -0.46536    0.44119  -1.055   0.2915  
SPIB        -0.37485    0.48838  -0.768   0.4428  
STAT1       -0.87887    0.81733  -1.075   0.2822  
TLR4         0.45989    0.64286   0.715   0.4744  
TNF          0.72373    0.98126   0.738   0.4608  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 177.69  on 146  degrees of freedom
Residual deviance: 142.78  on 114  degrees of freedom
AIC: 208.78

Number of Fisher Scoring iterations: 5
Setting levels: control = NR, case = R
Setting direction: controls < cases
Confusion Matrix and Statistics

         actual
predicted NR  R
       NR 17  9
       R  26 95
                                          
               Accuracy : 0.7619          
                 95% CI : (0.6847, 0.8282)
    No Information Rate : 0.7075          
    P-Value [Acc > NIR] : 0.085037        
                                          
                  Kappa : 0.3493          
                                          
 Mcnemar's Test P-Value : 0.006841        
                                          
            Sensitivity : 0.3953          
            Specificity : 0.9135          
         Pos Pred Value : 0.6538          
         Neg Pred Value : 0.7851          
             Prevalence : 0.2925          
         Detection Rate : 0.1156          
   Detection Prevalence : 0.1769          
      Balanced Accuracy : 0.6544          
                                          
       'Positive' Class : NR              
                                          

Logistic regression with random signatures


random_signatures <- lapply(1:1000,function(i){
  sample(unique_ortholog_mapping$ortholog_name,length(signature_human))
})

random_accuracy <- sapply(random_signatures, function(random_signature){
  logistic_glm(meta_MI, expr, random_signature)
})

ggplot(data.frame(random_accuracy=random_accuracy), aes(x=random_accuracy)) +
  geom_histogram(bins=30) +
  geom_vline(xintercept=accuracy, color="red")
LS0tCnRpdGxlOiAiSUJEIHByZWRpY3Rpb24iCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyfQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ3Byb2ZpbGVyMikKbGlicmFyeShnbG1uZXQpCmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmxpYnJhcnkocFJPQykKbGlicmFyeShjYXJldCkKbGlicmFyeShtaXhPbWljcykKbGlicmFyeShpbmZvdGhlbykKbGlicmFyeShjaXJjbGl6ZSkKbGlicmFyeShnckRldmljZXMpCnNldC5zZWVkKDEwMSkKYGBgCiMgRGF0YSBwcmVwcm9jZXNzaW5nCiMjIyBSZWFkIGRhdGEKYGBge3J9CiMgcmVhZCBtZXRhIGRhdGEKbWV0YSA8LSBhcy5kYXRhLnRhYmxlKHJlYWR4bDo6cmVhZF9leGNlbCgiZGF0YS90cmFuc2NyaXRvbWVfcGF0aWVudHNfdHJhbnNsYXRlZC54bHN4IikpCgptZXRhX2tleSA8LSAiQ0VMX0ZJTEUiCnNldGtleXYobWV0YSwgbWV0YV9rZXkpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHNldCBrZXkKc3RvcGlmbm90KCFhbnkoIGR1cGxpY2F0ZWQobWV0YVssLi5tZXRhX2tleV0pICkpICAgICMgY2hlY2sgZm9yIGR1cGxpY2F0ZSByb3dzCnN0b3BpZm5vdCghYW55KCBkdXBsaWNhdGVkKGNvbG5hbWVzKG1ldGEpKSApKSAgICAgICAjIGNoZWNrIGZvciBkdXBsaWNhdGUgY29sdW1ucwoKIyByZWFkIEZldWlsMSAKZmV1aWwxIDwtIGFzLmRhdGEudGFibGUocmVhZHhsOjpyZWFkX2V4Y2VsKCJkYXRhL3RyYW5zY3JpcHRvbWVfZXhwcmVzc2lvbl9tYXRyaXgueGxzeCIsc2hlZXQ9IkZldWlsMSIpKQoKIyByZWFkIGV4cHJlc3Npb24gZGF0YQpleHByIDwtIGFzLmRhdGEudGFibGUocmVhZHhsOjpyZWFkX2V4Y2VsKCJkYXRhL3RyYW5zY3JpcHRvbWVfZXhwcmVzc2lvbl9tYXRyaXgueGxzeCIpKQpleHByX2tleSA8LSAiZ2VuZSIKY29sbmFtZXMoZXhwcilbMV0gPC0gZXhwcl9rZXkKc2V0a2V5dihleHByLCBleHByX2tleSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgc2V0IGtleQpzdG9waWZub3QoIWFueSggZHVwbGljYXRlZChleHByWywuLmV4cHJfa2V5XSkgKSkgICAgIyBjaGVjayBmb3IgZHVwbGljYXRlIHJvd3MKc3RvcGlmbm90KCFhbnkoIGR1cGxpY2F0ZWQoY29sbmFtZXMoZXhwcikpICkpICAgICAgICMgY2hlY2sgZm9yIGR1cGxpY2F0ZSBjb2x1bW5zCgpleHByIDwtIGV4cHJbLCBjKGtleShleHByKSwgbWV0YSRDRUxfRklMRSksIHdpdGg9Rl0Kc3RvcGlmbm90KGFsbChjb2xuYW1lcyhleHByKVstMV09PW1ldGFbLENFTF9GSUxFXSkpCmV4cHIgPC0gYXMubWF0cml4KGV4cHIsIHJvd25hbWVzPSJnZW5lIikKCm1ldGFfTTAgPC0gbWV0YVtMT0NBVElPTj09Ik0wIl0KbWV0YV9NSSA8LSBtZXRhW0xPQ0FUSU9OPT0iTUkiXQptZXRhX002IDwtIG1ldGFbTE9DQVRJT049PSJNNiJdCm1ldGFfTTBNNiA8LSBtZXRhW0xPQ0FUSU9OPT0iTTYifExPQ0FUSU9OPT0iTTAiXQptZXRhX00wTUkgPC0gbWV0YVtMT0NBVElPTj09Ik0wInxMT0NBVElPTj09Ik1JIl0KbWV0YV9NME1JQyA8LSBtZXRhW0xPQ0FUSU9OPT0iTTAifExPQ0FUSU9OPT0iTUkifExPQ0FUSU9OPT0iQ3RybCJdCgojIHJlYWQgbW91c2Ugc2lnbmF0dXJlCnNpZ25hdHVyZV9kdCA8LSBmcmVhZCgiZGF0YS9zaWduYXR1cmUuY3N2IikKc2lnbmF0dXJlX21vdXNlIDwtIHNpZ25hdHVyZV9kdCRnZW5lCnNldGtleXYoc2lnbmF0dXJlX2R0LCAiZ2VuZSIpCgojIHJlYWQgYWxsIG1vdXNlIGdlbmVzIG1lYXN1cmVkIHdpdGggbmFub3N0cmluZyAKbWVhc3VyZWRfZ2VuZXNfbW91c2UgPC0gZnJlYWQoImRhdGEvQWxsIHNhbXBsZXNfTm9ybWFsaXplZERhdGEuY3N2IilbWzFdXQoKIyBjaGVjayB0aGF0IGFsbCBzaWduYXR1cmUgZ2VuZXMgYXJlIHBhcnQgb2YgdGhlIG1lYXN1cmVkIGdlbmVzCnN0b3BpZm5vdChhbGwoc2lnbmF0dXJlX21vdXNlICVpbiUgbWVhc3VyZWRfZ2VuZXNfbW91c2UpKQpgYGAKIyMjIE9ydGhvbG9nIG1hcHBpbmcKYGBge3J9CiMgbWFwIHRvIGh1bWFuIG9ydGhvbG9ncwpvcnRob2xvZ19tYXBwaW5nIDwtIGFzLmRhdGEudGFibGUoZ3Byb2ZpbGVyMjo6Z29ydGgobWVhc3VyZWRfZ2VuZXNfbW91c2UsIHNvdXJjZV9vcmdhbmlzbSA9ICJtbXVzY3VsdXMiLCB0YXJnZXRfb3JnYW5pc20gPSAiaHNhcGllbnMiLCBmaWx0ZXJfbmEgPSBGKSkKc3RvcGlmbm90KGFsbChtZWFzdXJlZF9nZW5lc19tb3VzZSAlaW4lIG9ydGhvbG9nX21hcHBpbmckaW5wdXQpKQoKIyBtYXJrIHNpZ25hdHVyZSBnZW5lcyBhbmQgaGl0cyBpbiB0aGUgaHVtYW4gZXhwciBkYXRhCm9ydGhvbG9nX21hcHBpbmckaW5fZXhwciA8LSBvcnRob2xvZ19tYXBwaW5nJG9ydGhvbG9nX25hbWUgJWluJSByb3duYW1lcyhleHByKQpvcnRob2xvZ19tYXBwaW5nJGluX3NpZ25hdHVyZSA8LSBvcnRob2xvZ19tYXBwaW5nJGlucHV0ICVpbiUgc2lnbmF0dXJlX21vdXNlIApvcnRob2xvZ19tYXBwaW5nW2luX3NpZ25hdHVyZT09VCxtb3VzZTo9c2lnbmF0dXJlX2R0W2lucHV0LHBhdHRlcm5dXQoKIyBjb3VudCBudW1iZXIgb2YgaGl0cyBpbiB0aGUgaHVtYW4gZXhwciBkYXRhIHBlciBtb3VzZSBnZW5lCm5faW5fZXhwciA8LSBvcnRob2xvZ19tYXBwaW5nWywuKCJuX2hpdHMiID0gc3VtKGluX2V4cHIpKSxieT1pbnB1dF0KCiMgcHJpbnQgbnVtYmVyIG9mIGhpdHMgaW4gZXhwciRnZW5lIHBlciBtZWFzdXJlZCBtb3VzZSBnZW5lCnRhYmxlKG5faW5fZXhwciRuX2hpdHMpCmBgYAoKYGBge3J9CiMgc2VsZWN0IG9ubHkgbW91c2UgZ2VuZXMgd2l0aCBvcnRob2xvZ3MgdW5pcXVlbHkgbWFwcGVkIHRvIHRoZSBodW1hbiBleHByZXNzaW9uIGRhdGEKdW5pcXVlX29ydGhvbG9nX21hcHBpbmcgPC0gb3J0aG9sb2dfbWFwcGluZ1tpbnB1dCAlaW4lIG5faW5fZXhwcltuX2hpdHM9PTEsaW5wdXRdICYgaW5fZXhwcl0KIyB1bm1hcHBlZCBzaWduYXR1cmUgZ2VuZXMKcHJpbnQocGFzdGUwKG5yb3codW5pcXVlX29ydGhvbG9nX21hcHBpbmcpLCIgb3V0IG9mICIsbGVuZ3RoKG1lYXN1cmVkX2dlbmVzX21vdXNlKSwgIiBtZWFzdXJlZCBtb3VzZSBnZW5lcyB3ZXJlIG1hcHBlZCB0byB0aGUgaHVtYW4gZXhwcmVzc2lvbiBnZW5lcyIpKQpvcnRob2xvZ19tYXBwaW5nWyFpbnB1dCAlaW4lIHVuaXF1ZV9vcnRob2xvZ19tYXBwaW5nJGlucHV0ICYgaW5fc2lnbmF0dXJlXQpgYGAKCiMgRGF0YSBjaGVja3MKIyMjIyBFeHByZXNzaW9uIHN1bSBwZXIgc2FtcGxlCmBgYHtyfQpwbG90X2RhdGEgPC0gZGF0YS50YWJsZShzYW1wbGU9Y29sbmFtZXMoZXhwcikpCnBsb3RfZGF0YVssY29sU3VtOj1jb2xTdW1zKGV4cHIpXQpwbG90X2RhdGFbLFJFQ1VSUkVOQ0U6PW1ldGEkUkVDVVJSRU5DRV0KcGxvdF9kYXRhWyxMT0NBVElPTjo9bWV0YSRMT0NBVElPTl0KcGxvdF9kYXRhWyxHRU5ERVI6PW1ldGEkR0VOREVSXQoKZ2dwbG90KHBsb3RfZGF0YSxhZXMoeD1jb2xTdW0sZmlsbD1SRUNVUlJFTkNFKSkgKwogIGdlb21fZGVuc2l0eShhbHBoYT0wLjUpCgpnZ3Bsb3QocGxvdF9kYXRhLGFlcyh4PWNvbFN1bSxmaWxsPUxPQ0FUSU9OKSkgKwogIGdlb21fZGVuc2l0eShhbHBoYT0wLjUpCgpnZ3Bsb3QocGxvdF9kYXRhLGFlcyh4PWNvbFN1bSxmaWxsPUdFTkRFUikpICsKICBnZW9tX2RlbnNpdHkoYWxwaGE9MC41KQpgYGAKIyMjIyBQQ0EKYGBge3J9CmhpZ2hfdmFyX2dlbmVzIDwtIHNvcnQoYXBwbHkoZXhwclssbWV0YSRDRUxfRklMRV0sIDEsIHNkKSwgZGVjcmVhc2luZyA9IFQpWzE6MTAwMDBdCmhpZ2hfdmFyX2V4cHIgPC0gdChleHByW25hbWVzKGhpZ2hfdmFyX2dlbmVzKSxtZXRhJENFTF9GSUxFXSkKCmhpZ2hfdmFyX3BjYSA8LSBwY2EoaGlnaF92YXJfZXhwciwgbmNvbXAgPSAzLCBzY2FsZSA9IFQpCnBsb3RJbmRpdihoaWdoX3Zhcl9wY2EsIGdyb3VwID0gbWV0YSRMT0NBVElPTiwgaW5kLm5hbWVzID0gRkFMU0UsCiAgICAgICAgICBsZWdlbmQgPSBUUlVFLCB0aXRsZT0iUENBIC0gaGlnaCB2YXJpYW5jZSBnZW5lcyIsIGVsbGlwc2UgPSBUKQoKc2lnbmF0dXJlX3BjYSA8LSBwY2EodChleHByW3VuaXF1ZV9vcnRob2xvZ19tYXBwaW5nW2luX2V4cHI9PVQgJiBpbl9zaWduYXR1cmUsIG9ydGhvbG9nX25hbWVdLCBtZXRhJENFTF9GSUxFXSksIG5jb21wID0gMywgc2NhbGUgPSBUUlVFKQpwbG90SW5kaXYoc2lnbmF0dXJlX3BjYSwgZ3JvdXAgPSBtZXRhJExPQ0FUSU9OLCBpbmQubmFtZXMgPSBGQUxTRSwKICAgICAgICAgIGxlZ2VuZCA9IFRSVUUsIHRpdGxlPSJQQ0EgLSBzaWduYXR1cmUgZ2VuZXMiLCBlbGxpcHNlID0gVCkKYGBgCgojIyMjIE1ldGEgY2hlY2tzCmBgYHtyfQp0YWJsZShtZXRhJFJFQ1VSUkVOQ0UsIG1ldGEkTE9DQVRJT04pCmBgYApgYGB7cn0KdGFibGUobWV0YSRMT0NBVElPTikKYGBgCkN0cmwgKDI1KSAtPiBDdHJsICgyNSkKTTAgICgxOTYpIC0+IE0wSSAoMjAwKQpNSSAgKDE0NykgLT4gTTBNICgxNDkpCk02ICAoMTIxKSAtPiBNNiAgKDEyMikKV2h5IGRvIHRoZSBudW1iZXJzIGZyb20gcHVibGljYXRpb24gYW5kIG1ldGEgc2hlZXQgbm90IG1hdGNoPwoKV2hhdCBpcyBDRU5UUkU/CldoYXQgYXJlIHN0ZW5vc2UsIGZpc3R1bGUsIGluZmxhbW1hdG9pcmUsIFN0b21hPwpXaGF0IGlzIFBvc3RvcGVyYXRpdmUgYW50aS1UTkY/CgpXaHkgZG8gUnV0Z2VlcnRSZWMgYW5kIFJFQ1VSUkVOQ0Ugbm90IG1hdGNoIDEgdG8gMT8KYGBge3J9CnRhYmxlKG1ldGEkUnV0Z2VlcnRSZWMsIG1ldGEkUkVDVVJSRU5DRSkKYGBgCgoKCiMjIyMgU2lnbmF0dXJlIGhlYXRtYXAKYGBge3J9CmN1c3RvbV9oZWF0bWFwIDwtIGZ1bmN0aW9uKGV4cHIsIG1ldGEsIGdlbmVzLCBjb2x1bW5fbmFtZXMgPSBGLCBzY2FsZSA9IFQsIC4uLil7CiAgCiAgaWYoaXMubnVsbChtZXRhKSl7CiAgICBzYW1wbGVzIDwtIGNvbG5hbWVzKGV4cHIpCiAgICAgIGNvbHVtbl9oYSA9IE5VTEwKICB9ZWxzZXsKICAgIG1ldGEgPC0gYXMuZGF0YS5mcmFtZShtZXRhLCByb3cubmFtZXMgPSBtZXRhW1sxXV0pWywtMSxkcm9wPUZdCiAgICBzYW1wbGVzIDwtIHJvd25hbWVzKG1ldGEpCiAgICBjb2x1bW5faGEgPSBIZWF0bWFwQW5ub3RhdGlvbihkZj1tZXRhKQogIH0KICAKICBnZW5lcyA8LSBhcy5kYXRhLmZyYW1lKGdlbmVzLCByb3cubmFtZXMgPSBnZW5lc1tbMV1dKVssLTEsZHJvcD1GXQogIGdlbmVfbmFtZXMgPC0gcm93bmFtZXMoZ2VuZXMpCiAgcm93X2hhID0gcm93QW5ub3RhdGlvbigKICAgIGRmPWdlbmVzLCAKICAgIGFubm90YXRpb25fbmFtZV9zaWRlPSJ0b3AiLAogICAgI2Fubm90YXRpb25fbGFiZWw9KCJcdTAzOTQvXHUwMzk0SUVDIiksCiAgICBjb2wgPSBsaXN0KG1vdXNlPSBjKCJ1cCIgPSAiI0U4RTcwMCIsICJkb3duIiA9ICIjMDA5MkY0IikpCiAgKQogIAogIGlmKHNjYWxlKXsKICAgIGV4cHIgPC0gdChhcHBseShleHByW2dlbmVfbmFtZXMsc2FtcGxlc10sMSxzY2FsZSkpCiAgICBjb2xuYW1lcyhleHByKSA8LSBzYW1wbGVzCiAgfWVsc2V7CiAgICBleHByIDwtIGV4cHJbZ2VuZV9uYW1lcyxzYW1wbGVzXQogIH0KICAKICAKICBIZWF0bWFwKGV4cHIsCiAgICAgICAgc2hvd19jb2x1bW5fbmFtZXMgPSBjb2x1bW5fbmFtZXMsIAogICAgICAgIHRvcF9hbm5vdGF0aW9uID0gY29sdW1uX2hhLAogICAgICAgIG5hbWUgPSAiRXhwcmVzc2lvbiIsCiAgICAgICAgcm93X25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDEwKSwKICAgICAgICBsZWZ0X2Fubm90YXRpb24gPSByb3dfaGEsCiAgICAgICAgY29sdW1uX3RpdGxlX3NpZGUgPSAidG9wIiwKICAgICAgICAuLi4KICAgICAgICApCn0KYGBgCgpgYGB7cn0KCgpjdXN0b21faGVhdG1hcCgKICBleHByLCAKICBtZXRhW0xPQ0FUSU9OPT0iTTAiLC4oQ0VMX0ZJTEUsUkVDVVJSRU5DRSxMT0NBVElPTixpbmZsYW1tYXRvaXJlLFNtb2tlcixHcmFudWxvbWEsUnV0Z2VlcnQyKV0sCiAgdW5pcXVlX29ydGhvbG9nX21hcHBpbmdbaW5fZXhwcj09VCZpbl9zaWduYXR1cmU9PVQsLihvcnRob2xvZ19uYW1lLG1vdXNlKV0sCiAgY2x1c3Rlcl9jb2x1bW5zID0gVCkKCmN1c3RvbV9oZWF0bWFwKAogIGV4cHIsIAogIG1ldGFbTE9DQVRJT049PSJNNiIsLihDRUxfRklMRSxSRUNVUlJFTkNFLExPQ0FUSU9OLFJlZWNhbF9SdXQ9YXMubnVtZXJpYyhSZWV2YWxfUnV0KSldLAogIHVuaXF1ZV9vcnRob2xvZ19tYXBwaW5nW2luX2V4cHI9PVQmaW5fc2lnbmF0dXJlPT1ULC4ob3J0aG9sb2dfbmFtZSxtb3VzZSldLAogIGNsdXN0ZXJfY29sdW1ucyA9IFQpCgpjdXN0b21faGVhdG1hcCgKICBleHByLCAKICBtZXRhW0xPQ0FUSU9OPT0iTTYiLC4oQ0VMX0ZJTEUsUkVDVVJSRU5DRSxMT0NBVElPTildW29yZGVyKFJFQ1VSUkVOQ0UpXSwKICB1bmlxdWVfb3J0aG9sb2dfbWFwcGluZ1tpbl9leHByPT1UJmluX3NpZ25hdHVyZT09VCwuKG9ydGhvbG9nX25hbWUsbW91c2UpXSwKICBjbHVzdGVyX2NvbHVtbnMgPSBGKQoKY3VzdG9tX2hlYXRtYXAoCiAgZXhwciwgCiAgbWV0YVtMT0NBVElPTj09Ik0wInxMT0NBVElPTj09Ik1JIiwuKENFTF9GSUxFLExPQ0FUSU9OKV0sCiAgdW5pcXVlX29ydGhvbG9nX21hcHBpbmdbaW5fZXhwcj09VCZpbl9zaWduYXR1cmU9PVQsLihvcnRob2xvZ19uYW1lLG1vdXNlKV0sCiAgY2x1c3Rlcl9jb2x1bW5zID0gVCkKCmN1c3RvbV9oZWF0bWFwKAogIGV4cHIsIAogIG1ldGFbTE9DQVRJT049PSJNMCJ8TE9DQVRJT049PSJNSSIsLihDRUxfRklMRSxMT0NBVElPTildW29yZGVyKExPQ0FUSU9OKV0sCiAgdW5pcXVlX29ydGhvbG9nX21hcHBpbmdbaW5fZXhwcj09VCZpbl9zaWduYXR1cmU9PVQsLihvcnRob2xvZ19uYW1lLG1vdXNlKV0sCiAgY2x1c3Rlcl9jb2x1bW5zID0gRikKCmN1c3RvbV9oZWF0bWFwKAogIGV4cHIsIAogIG1ldGFbTE9DQVRJT049PSJNMCJ8TE9DQVRJT049PSJNSSIsLihDRUxfRklMRSxMT0NBVElPTildW29yZGVyKExPQ0FUSU9OKV0sCiAgdW5pcXVlX29ydGhvbG9nX21hcHBpbmdbaW5fZXhwcj09VCZpbl9zaWduYXR1cmU9PVQsLihvcnRob2xvZ19uYW1lLG1vdXNlKV1bb3JkZXIobW91c2UpXSwKICBjbHVzdGVyX2NvbHVtbnMgPSBGLCBjbHVzdGVyX3Jvd3MgPSBGKQoKCmBgYAojIyMjIExvY2F0aW9uIHN1bW1hcml6ZWQgaGVhdG1hcAoKYGBge3J9CiMgTUksIE0wLCBDdHJsCmV4cHJfc2NhbGVkIDwtIGFwcGx5KGV4cHJbdW5pcXVlX29ydGhvbG9nX21hcHBpbmdbaW5fZXhwcj09VCZpbl9zaWduYXR1cmU9PVQsb3J0aG9sb2dfbmFtZV0sbWV0YV9NME1JQyRDRUxfRklMRV0gLDEsc2NhbGUpCnJvd25hbWVzKGV4cHJfc2NhbGVkKSA8LSBtZXRhX00wTUlDJENFTF9GSUxFCgpzdW1tYXJpemVkX2V4cHIgPC0gc2FwcGx5KHVuaXF1ZShtZXRhX00wTUlDJExPQ0FUSU9OKSwgZnVuY3Rpb24obG9jYXRpb24pewogIGNvbE1lYW5zKGV4cHJfc2NhbGVkW21ldGFfTTBNSUNbTE9DQVRJT049PWxvY2F0aW9uLENFTF9GSUxFXSxdKQp9KQoKIyBtdXR1YWwgaW5mb3JtYXRpb24KY29uY29yZGFuY2UgPC0gZGF0YS5mcmFtZSgKICBtb3VzZSA9IHVuaXF1ZV9vcnRob2xvZ19tYXBwaW5nW2luX3NpZ25hdHVyZT09VCxtb3VzZV0sCiAgaHVtYW4gPSBpZmVsc2Uoc2lnbihzdW1tYXJpemVkX2V4cHJbdW5pcXVlX29ydGhvbG9nX21hcHBpbmdbaW5fc2lnbmF0dXJlPT1ULG9ydGhvbG9nX25hbWVdLCJNMCJdKT4wLCAidXAiLCAiZG93biIpCiAgKQpjb25jb3JkYW5jZSRjb25jb3JkYW5jZSA8LSBjb25jb3JkYW5jZSRtb3VzZSA9PSBjb25jb3JkYW5jZSRodW1hbgptaSA8LSBtdXRpbmZvcm1hdGlvbihjb25jb3JkYW5jZSRtb3VzZSxjb25jb3JkYW5jZSRodW1hbikKcmFuZG9tX21pIDwtIHNhcHBseSgxOjEwMDAsIGZ1bmN0aW9uKGkpe211dGluZm9ybWF0aW9uKHNhbXBsZShjb25jb3JkYW5jZSRtb3VzZSksY29uY29yZGFuY2UkaHVtYW4pfSkKbWlfcHZhbCA8LSAoc3VtKHJhbmRvbV9taT5taSkgKyAxKS8obGVuZ3RoKHJhbmRvbV9taSkrMSkKcGFzdGUoImNvbmNvcmRhbnQgZ2VuZXM6IixzdW0oY29uY29yZGFuY2UkY29uY29yZGFuY2UpLCJtdXR1YWwgaW5mb3JtYXRpb246IixtaSwicC12YWx1ZSIsbWlfcHZhbCkKCmN1c3RvbV9oZWF0bWFwKHN1bW1hcml6ZWRfZXhwciwgTlVMTCwgdW5pcXVlX29ydGhvbG9nX21hcHBpbmdbaW5fc2lnbmF0dXJlPT1ULC4ob3J0aG9sb2dfbmFtZSxtb3VzZSxjb25jb3JkYW5jZT1jb25jb3JkYW5jZSRjb25jb3JkYW5jZSldICxjb2x1bW5fbmFtZXM9VCwgc2NhbGUgPSBGKQpjdXN0b21faGVhdG1hcChzdW1tYXJpemVkX2V4cHIsIE5VTEwsIHVuaXF1ZV9vcnRob2xvZ19tYXBwaW5nW2luX3NpZ25hdHVyZT09VCwuKG9ydGhvbG9nX25hbWUsbW91c2UsY29uY29yZGFuY2U9Y29uY29yZGFuY2UkY29uY29yZGFuY2UpXVtvcmRlcihtb3VzZSldLGNsdXN0ZXJfcm93cyA9IEYsIGNvbHVtbl9uYW1lcz1ULCBzY2FsZSA9IEYpCgoKIyBNSSwgTTAKZXhwcl9zY2FsZWQgPC0gYXBwbHkoZXhwclt1bmlxdWVfb3J0aG9sb2dfbWFwcGluZ1tpbl9leHByPT1UJmluX3NpZ25hdHVyZT09VCxvcnRob2xvZ19uYW1lXSxtZXRhX00wTUkkQ0VMX0ZJTEVdICwxLHNjYWxlKQpyb3duYW1lcyhleHByX3NjYWxlZCkgPC0gbWV0YV9NME1JJENFTF9GSUxFCgpzdW1tYXJpemVkX2V4cHIgPC0gc2FwcGx5KHVuaXF1ZShtZXRhX00wTUkkTE9DQVRJT04pLCBmdW5jdGlvbihsb2NhdGlvbil7CiAgY29sTWVhbnMoZXhwcl9zY2FsZWRbbWV0YV9NME1JW0xPQ0FUSU9OPT1sb2NhdGlvbixDRUxfRklMRV0sXSkKfSkKCiMgbXV0dWFsIGluZm9ybWF0aW9uCmNvbmNvcmRhbmNlIDwtIGRhdGEuZnJhbWUoCiAgbW91c2UgPSB1bmlxdWVfb3J0aG9sb2dfbWFwcGluZ1tpbl9zaWduYXR1cmU9PVQsbW91c2VdLAogIGh1bWFuID0gaWZlbHNlKHNpZ24oc3VtbWFyaXplZF9leHByW3VuaXF1ZV9vcnRob2xvZ19tYXBwaW5nW2luX3NpZ25hdHVyZT09VCxvcnRob2xvZ19uYW1lXSwiTTAiXSk+MCwgInVwIiwgImRvd24iKQogICkKY29uY29yZGFuY2UkY29uY29yZGFuY2UgPC0gY29uY29yZGFuY2UkbW91c2UgPT0gY29uY29yZGFuY2UkaHVtYW4KbWkgPC0gbXV0aW5mb3JtYXRpb24oY29uY29yZGFuY2UkbW91c2UsY29uY29yZGFuY2UkaHVtYW4pCnJhbmRvbV9taSA8LSBzYXBwbHkoMToxMDAwLCBmdW5jdGlvbihpKXttdXRpbmZvcm1hdGlvbihzYW1wbGUoY29uY29yZGFuY2UkbW91c2UpLGNvbmNvcmRhbmNlJGh1bWFuKX0pCm1pX3B2YWwgPC0gKHN1bShyYW5kb21fbWk+bWkpICsgMSkvKGxlbmd0aChyYW5kb21fbWkpKzEpCnBhc3RlKCJjb25jb3JkYW50IGdlbmVzOiIsc3VtKGNvbmNvcmRhbmNlJGNvbmNvcmRhbmNlKSwibXV0dWFsIGluZm9ybWF0aW9uOiIsbWksInAtdmFsdWUiLG1pX3B2YWwpCgpjdXN0b21faGVhdG1hcChzdW1tYXJpemVkX2V4cHIsIE5VTEwsIHVuaXF1ZV9vcnRob2xvZ19tYXBwaW5nW2luX3NpZ25hdHVyZT09VCwuKG9ydGhvbG9nX25hbWUsbW91c2UsY29uY29yZGFuY2U9Y29uY29yZGFuY2UkY29uY29yZGFuY2UpXSAsY29sdW1uX25hbWVzPVQsIHNjYWxlID0gRikKY3VzdG9tX2hlYXRtYXAoc3VtbWFyaXplZF9leHByLCBOVUxMLCB1bmlxdWVfb3J0aG9sb2dfbWFwcGluZ1tpbl9zaWduYXR1cmU9PVQsLihvcnRob2xvZ19uYW1lLG1vdXNlLGNvbmNvcmRhbmNlPWNvbmNvcmRhbmNlJGNvbmNvcmRhbmNlKV1bb3JkZXIobW91c2UpXSxjbHVzdGVyX3Jvd3MgPSBGLCBjb2x1bW5fbmFtZXM9VCwgc2NhbGUgPSBGKQoKYGBgCgoKIyBGb3IgcGFwZXIKYGBge3J9CgojIHNjYWxlIGV4cHJlc3Npb24gZGF0YQpleHByX3NjYWxlZCA8LSBhcHBseShleHByW3VuaXF1ZV9vcnRob2xvZ19tYXBwaW5nW2luX3NpZ25hdHVyZT09VCxvcnRob2xvZ19uYW1lXSxtZXRhX00wTUkkQ0VMX0ZJTEVdICwxLHNjYWxlKQpyb3duYW1lcyhleHByX3NjYWxlZCkgPC0gbWV0YV9NME1JJENFTF9GSUxFCgojIG1lYW4gcGVyIGxvY2F0aW9uCnN1bW1hcml6ZWRfZXhwciA8LSBzYXBwbHkodW5pcXVlKGMoIk1JIiwiTTAiKSksIGZ1bmN0aW9uKGxvY2F0aW9uKXsKICBjb2xNZWFucyhleHByX3NjYWxlZFttZXRhX00wTUlbTE9DQVRJT049PWxvY2F0aW9uLENFTF9GSUxFXSxdKQp9KQoKIyBjb25jb3JkYW5jZSBkYXRhCmNvbmNvcmRhbmNlIDwtIGRhdGEuZnJhbWUoCiAgbW91c2UgPSB1bmlxdWVfb3J0aG9sb2dfbWFwcGluZ1tpbl9zaWduYXR1cmU9PVQsbW91c2VdLAogIGh1bWFuID0gaWZlbHNlKHNpZ24oc3VtbWFyaXplZF9leHByW3VuaXF1ZV9vcnRob2xvZ19tYXBwaW5nW2luX3NpZ25hdHVyZT09VCxvcnRob2xvZ19uYW1lXSwiTTAiXSk+MCwgInVwIiwgImRvd24iKQogICkKY29uY29yZGFuY2UkY29uY29yZGFuY2UgPC0gY29uY29yZGFuY2UkbW91c2UgPT0gY29uY29yZGFuY2UkaHVtYW4KbWkgPC0gbXV0aW5mb3JtYXRpb24oY29uY29yZGFuY2UkbW91c2UsY29uY29yZGFuY2UkaHVtYW4pCnNldC5zZWVkKDApCnJhbmRvbV9taSA8LSBzYXBwbHkoMToxMDAwMCwgZnVuY3Rpb24oaSl7bXV0aW5mb3JtYXRpb24oc2FtcGxlKGNvbmNvcmRhbmNlJG1vdXNlKSxjb25jb3JkYW5jZSRodW1hbil9KQptaV9wdmFsIDwtIChzdW0ocmFuZG9tX21pPm1pKSArIDEpLyhsZW5ndGgocmFuZG9tX21pKSsxKQpwYXN0ZSgiY29uY29yZGFudCBnZW5lczoiLHN1bShjb25jb3JkYW5jZSRjb25jb3JkYW5jZSksIm11dHVhbCBpbmZvcm1hdGlvbjoiLG1pLCJwLXZhbHVlIixtaV9wdmFsKQoKY29uY29yZGFuY2UkTWljZSA8LSBpZmVsc2UoY29uY29yZGFuY2UkbW91c2U9PSJ1cCIsICJ1cCBpbiBcdTAzOTQvXHUwMzk0SUVDIiwgImRvd24gaW4gXHUwMzk0L1x1MDM5NElFQyIpCmdyRGV2aWNlczo6Y2Fpcm9fcGRmKCJoZWF0bWFwLnBkZiIsIHdpZHRoID0gNCwgaGVpZ2h0ID0gNywpCiMgcm93IGFubm90YXRpb24Kcm93X2hhID0gcm93QW5ub3RhdGlvbigKICBNaWNlPWNvbmNvcmRhbmNlJE1pY2UsIAogIGFubm90YXRpb25fbmFtZV9zaWRlPSJ0b3AiLAogIGFubm90YXRpb25fbmFtZV9yb3Q9MCwKICBjb2wgPSBsaXN0KE1pY2U9IGMoInVwIGluIFx1MDM5NC9cdTAzOTRJRUMiID0gIiNFOEU3MDAiLCAiZG93biBpbiBcdTAzOTQvXHUwMzk0SUVDIiA9ICIjMDA5MkY0IikpCikKCmNvbF9mdW4gPSBjb2xvclJhbXAyKGMoLTAuNSwgMCwgMC41KSwgYygiYmx1ZSIsICJ3aGl0ZSIsICJyZWQiKSkKCmNvbmNvcmRhbmNlJGNvbCA8LSAiZ3JleSIKY29uY29yZGFuY2VbY29uY29yZGFuY2UkY29uY29yZGFuY2UgJiBjb25jb3JkYW5jZSRtb3VzZT09InVwIixdJGNvbCA8LSAicmVkIgpjb25jb3JkYW5jZVtjb25jb3JkYW5jZSRjb25jb3JkYW5jZSAmIGNvbmNvcmRhbmNlJG1vdXNlPT0iZG93biIsXSRjb2wgPC0gImJsdWUiCgpIZWF0bWFwKHN1bW1hcml6ZWRfZXhwciwKICBuYW1lID0gIkV4cHJlc3Npb24iLAogIHJvd19uYW1lc19ncCA9IGdwYXIoZm9udHNpemUgPSA5LCBjb2wgPSBjb25jb3JkYW5jZSRjb2wpLAogIGxlZnRfYW5ub3RhdGlvbiA9IHJvd19oYSwKICBjb2x1bW5fdGl0bGVfc2lkZSA9ICJ0b3AiLAogIGNsdXN0ZXJfY29sdW1ucyA9IEYsCiAgY29sdW1uX2xhYmVscyA9IGMoIk0wTSIsIk0wSSIpLAogIGNvbHVtbl9uYW1lc19zaWRlID0gInRvcCIsCiAgY29sdW1uX25hbWVzX3JvdCA9IDAsCiAgY29sdW1uX25hbWVzX2NlbnRlcmVkID0gVCwKICBjb2wgPSBjb2xfZnVuLAogIHdpZHRoID0gdW5pdCgzLCAiY20iKSwKICApCmRldi5vZmYoKQogIApjdXN0b21faGVhdG1hcChzdW1tYXJpemVkX2V4cHIsIE5VTEwsIHVuaXF1ZV9vcnRob2xvZ19tYXBwaW5nW2luX3NpZ25hdHVyZT09VCwuKG9ydGhvbG9nX25hbWUsbW91c2UsY29uY29yZGFuY2U9Y29uY29yZGFuY2UkY29uY29yZGFuY2UpXSAsY29sdW1uX25hbWVzPVQsIHNjYWxlID0gRikKCmBgYAoKCiMgUExTLURBIEFuYWx5c2lzCmBgYHtyfQpoaWdoX3Zhcl9nZW5lcyA8LSBzb3J0KGFwcGx5KGV4cHJbLG1ldGFfTTAkQ0VMX0ZJTEVdLCAxLCBzZCksIGRlY3JlYXNpbmcgPSBUKVsxOjUwMDBdCnBsc19kYV9leHByIDwtIHQoZXhwcltuYW1lcyhoaWdoX3Zhcl9nZW5lcyksbWV0YV9NMCRDRUxfRklMRV0pCiNwbHNfZGFfZXhwciA8LSAyIF4gcGxzX2RhX2V4cHIKCnBjYS5leHByIDwtIHBjYShwbHNfZGFfZXhwciwgbmNvbXAgPSAzLCBzY2FsZSA9IFRSVUUpCnBsb3RJbmRpdihwY2EuZXhwciwgZ3JvdXAgPSBtZXRhX00wJFJFQ1VSUkVOQ0UsIGluZC5uYW1lcyA9IEZBTFNFLAogICAgICAgICAgbGVnZW5kID0gVFJVRSwgCiAgICAgICAgICB0aXRsZSA9ICdQQ0EgY29tcCAxIC0gMicpCgpwbHNkYS5leHByIDwtIHBsc2RhKHBsc19kYV9leHByLCBtZXRhX00wJFJFQ1VSUkVOQ0UsIG5jb21wID0gMTApCgpwZXJmLnBsc2RhLmV4cHIgPC0gcGVyZihwbHNkYS5leHByLCB2YWxpZGF0aW9uID0gJ01mb2xkJywgZm9sZHMgPSAzLCAKICAgICAgICAgICAgICAgICAgcHJvZ3Jlc3NCYXIgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgbnJlcGVhdCA9IDEwKSAgICAgICAgIAoKcGxvdChwZXJmLnBsc2RhLmV4cHIsIHNkID0gVFJVRSwgbGVnZW5kLnBvc2l0aW9uID0gJ2hvcml6b250YWwnKQpgYGAKIyBNMCB2cyBNSSBwcmVkaWN0aW9uCmBgYHtyfQpsb2dpc3RpY19nbG1uZXRfbG9jIDwtIGZ1bmN0aW9uKG1ldGEsIGV4cHIsIHNpZ25hdHVyZSl7CiAgCiAgeCA8LSB0KGV4cHJbc2lnbmF0dXJlLCBtZXRhJENFTF9GSUxFXSkKICB5IDwtIG1ldGEkTE9DQVRJT04KICAKICBmaXQgPC0gZ2xtbmV0KHgsIHksIGZhbWlseSA9ICJiaW5vbWlhbCIpCiAgcGxvdChmaXQsIGxhYmVsID0gVCkKICBjdmZpdCA8LSBjdi5nbG1uZXQoeCwgeSwgZmFtaWx5ID0gImJpbm9taWFsIikKICBwbG90KGN2Zml0KQogIHByaW50KGN2Zml0KQogIHByaW50KGNvZWYoY3ZmaXQsIHMgPSAibGFtYmRhLjFzZSIpKQp9Cgpsb2dpc3RpY19nbG1fbG9jIDwtIGZ1bmN0aW9uKG1ldGEsIGV4cHIsIHNpZ25hdHVyZSwgcm9jPUYsIHN1bW1hcnk9RiwgcGVyZm9ybWFuY2U9Ril7CiAgCiAgeCA8LSB0KGV4cHJbc2lnbmF0dXJlLCBtZXRhJENFTF9GSUxFXSkKICB5IDwtIG1ldGEkTE9DQVRJT04KICAKICBkYXRhIDwtIGFzLmRhdGEuZnJhbWUoeCkKICBkYXRhJExPQ0FUSU9OIDwtIDAKICBkYXRhJExPQ0FUSU9OW3k9PSJNMCJdIDwtIDEKICAKICBnbG1fbW9kZWwgPC0gZ2xtKExPQ0FUSU9OIH4uLGZhbWlseSA9ICJiaW5vbWlhbCIsIGRhdGEpCiAgCiAgIyBwcmVkaWN0aW9uCiAgbW9kZWxfcHJvYiA9IHByZWRpY3QoZ2xtX21vZGVsLCB0eXBlID0gInJlc3BvbnNlIikKICBtb2RlbF9wcmVkID0gaWZlbHNlKG1vZGVsX3Byb2IgPiAwLjUsICJNMCIsICJNSSIpCiAgdHJhaW5fdGFiID0gdGFibGUocHJlZGljdGVkID0gbW9kZWxfcHJlZCwgYWN0dWFsID0geSkKICB0cmFpbl9jb25fbWF0ID0gY29uZnVzaW9uTWF0cml4KHRyYWluX3RhYikKICAKICBpZihzdW1tYXJ5KXsKICAgIHByaW50KHN1bW1hcnkoZ2xtX21vZGVsKSkKICB9CiAgCiAgaWYocm9jKXsKICAgIHJvYyh5IH4gbW9kZWxfcHJvYiwgcGxvdCA9IFRSVUUsIHByaW50LmF1YyA9IFRSVUUpCiAgfQoKICBpZihwZXJmb3JtYW5jZSl7CiAgICBwcmludCh0cmFpbl9jb25fbWF0KQogIH0KICAKICB0cmFpbl9jb25fbWF0JG92ZXJhbGxbIkFjY3VyYWN5Il0KfQpgYGAKCmBgYHtyfQpzaWduYXR1cmVfaHVtYW4gPC0gdW5pcXVlX29ydGhvbG9nX21hcHBpbmdbaW5fc2lnbmF0dXJlPT1ULG9ydGhvbG9nX25hbWVdCmxvZ2lzdGljX2dsbW5ldF9sb2MobWV0YV9NME1JLCBleHByLCBzaWduYXR1cmVfaHVtYW4pCmBgYApMb2dpc3RpYyByZWdyZXNzaW9uIHdpdGggZnVsbCBzaWduYXR1cmUKYGBge3J9CiMgQW5hbHlzaXMgd2l0aCBnbG0KYWNjdXJhY3kgPC0gbG9naXN0aWNfZ2xtX2xvYyhtZXRhX00wTUksIGV4cHIsIHNpZ25hdHVyZV9odW1hbiwgVCwgVCwgVCkKYGBgCkxvZ2lzdGljIHJlZ3Jlc3Npb24gd2l0aCByYW5kb20gc2lnbmF0dXJlcwpgYGB7cn0KCnJhbmRvbV9zaWduYXR1cmVzIDwtIGxhcHBseSgxOjEwMDAsZnVuY3Rpb24oaSl7CiAgc2FtcGxlKHVuaXF1ZV9vcnRob2xvZ19tYXBwaW5nJG9ydGhvbG9nX25hbWUsbGVuZ3RoKHNpZ25hdHVyZV9odW1hbikpCn0pCgpyYW5kb21fYWNjdXJhY3kgPC0gc2FwcGx5KHJhbmRvbV9zaWduYXR1cmVzLCBmdW5jdGlvbihyYW5kb21fc2lnbmF0dXJlKXsKICBsb2dpc3RpY19nbG1fbG9jKG1ldGFfTTBNSSwgZXhwciwgcmFuZG9tX3NpZ25hdHVyZSkKfSkKCmdncGxvdChkYXRhLmZyYW1lKHJhbmRvbV9hY2N1cmFjeT1yYW5kb21fYWNjdXJhY3kpLCBhZXMoeD1yYW5kb21fYWNjdXJhY3kpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucz0zMCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hY2N1cmFjeSwgY29sb3I9InJlZCIpCgooc3VtKHJhbmRvbV9hY2N1cmFjeT5hY2N1cmFjeSkrMSkvKGxlbmd0aChyYW5kb21fc2lnbmF0dXJlcykrMSkKYGBgCgojIFJlY3VycmVuY2UgcHJlZGljdGlvbgpgYGB7cn0KbG9naXN0aWNfZ2xtbmV0IDwtIGZ1bmN0aW9uKG1ldGEsIGV4cHIsIHNpZ25hdHVyZSl7CiAgCiAgeCA8LSB0KGV4cHJbc2lnbmF0dXJlLCBtZXRhJENFTF9GSUxFXSkKICB5IDwtIG1ldGEkUkVDVVJSRU5DRQogIAogIGZpdCA8LSBnbG1uZXQoeCwgeSwgZmFtaWx5ID0gImJpbm9taWFsIikKICBwbG90KGZpdCwgbGFiZWwgPSBUKQogIGN2Zml0IDwtIGN2LmdsbW5ldCh4LCB5LCBmYW1pbHkgPSAiYmlub21pYWwiLCB0eXBlLm1lYXN1cmUgPSAiY2xhc3MiKQogIHBsb3QoY3ZmaXQpCiAgcHJpbnQoY3ZmaXQpCn0KCmxvZ2lzdGljX2dsbSA8LSBmdW5jdGlvbihtZXRhLCBleHByLCBzaWduYXR1cmUsIHJvYz1GLCBzdW1tYXJ5PUYsIHBlcmZvcm1hbmNlPUYpewogIAogIHggPC0gdChleHByW3NpZ25hdHVyZSwgbWV0YSRDRUxfRklMRV0pCiAgeSA8LSBtZXRhJFJFQ1VSUkVOQ0UKICAKICBkYXRhIDwtIGFzLmRhdGEuZnJhbWUoeCkKICBkYXRhJFJFQ1VSUkVOQ0UgPC0gMAogIGRhdGEkUkVDVVJSRU5DRVt5PT0iUiJdIDwtIDEKICAKICBnbG1fbW9kZWwgPC0gZ2xtKFJFQ1VSUkVOQ0Ugfi4sZmFtaWx5ID0gImJpbm9taWFsIiwgZGF0YSkKICAKICAjIHByZWRpY3Rpb24KICBtb2RlbF9wcm9iID0gcHJlZGljdChnbG1fbW9kZWwsIHR5cGUgPSAicmVzcG9uc2UiKQogIG1vZGVsX3ByZWQgPSBpZmVsc2UobW9kZWxfcHJvYiA+IDAuNSwgIlIiLCAiTlIiKQogIHRyYWluX3RhYiA9IHRhYmxlKHByZWRpY3RlZCA9IG1vZGVsX3ByZWQsIGFjdHVhbCA9IHkpCiAgdHJhaW5fY29uX21hdCA9IGNvbmZ1c2lvbk1hdHJpeCh0cmFpbl90YWIpCiAgCiAgaWYoc3VtbWFyeSl7CiAgICBwcmludChzdW1tYXJ5KGdsbV9tb2RlbCkpCiAgfQogIAogIGlmKHJvYyl7CiAgICByb2MoeSB+IG1vZGVsX3Byb2IsIHBsb3QgPSBUUlVFLCBwcmludC5hdWMgPSBUUlVFKQogIH0KCiAgaWYocGVyZm9ybWFuY2UpewogICAgcHJpbnQodHJhaW5fY29uX21hdCkKICB9CiAgCiAgdHJhaW5fY29uX21hdCRvdmVyYWxsWyJBY2N1cmFjeSJdCn0KYGBgCgpgYGB7cn0Kc2lnbmF0dXJlX2h1bWFuIDwtIHVuaXF1ZV9vcnRob2xvZ19tYXBwaW5nW2luX3NpZ25hdHVyZT09VCxvcnRob2xvZ19uYW1lXQpgYGAKCiMjIE0wCkZlYXR1cmUgc2VsZWN0aW9uIHdpdGggbG9naXN0aWMgZ2xtbmV0CmBgYHtyfQpsb2dpc3RpY19nbG1uZXQobWV0YV9NMCwgZXhwciwgc2lnbmF0dXJlX2h1bWFuKQpgYGAKCkxvZ2lzdGljIHJlZ3Jlc3Npb24gd2l0aCBmdWxsIHNpZ25hdHVyZQpgYGB7cn0KIyBBbmFseXNpcyB3aXRoIGdsbQphY2N1cmFjeSA8LSBsb2dpc3RpY19nbG0obWV0YV9NMCwgZXhwciwgc2lnbmF0dXJlX2h1bWFuLCBULCBULCBUKQpgYGAKTG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIHJhbmRvbSBzaWduYXR1cmVzCmBgYHtyfQoKcmFuZG9tX3NpZ25hdHVyZXMgPC0gbGFwcGx5KDE6MTAwMCxmdW5jdGlvbihpKXsKICBzYW1wbGUodW5pcXVlX29ydGhvbG9nX21hcHBpbmckb3J0aG9sb2dfbmFtZSxsZW5ndGgoc2lnbmF0dXJlX2h1bWFuKSkKfSkKCnJhbmRvbV9hY2N1cmFjeSA8LSBzYXBwbHkocmFuZG9tX3NpZ25hdHVyZXMsIGZ1bmN0aW9uKHJhbmRvbV9zaWduYXR1cmUpewogIGxvZ2lzdGljX2dsbShtZXRhX00wLCBleHByLCByYW5kb21fc2lnbmF0dXJlKQp9KQoKZ2dwbG90KGRhdGEuZnJhbWUocmFuZG9tX2FjY3VyYWN5PXJhbmRvbV9hY2N1cmFjeSksIGFlcyh4PXJhbmRvbV9hY2N1cmFjeSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zPTMwKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFjY3VyYWN5LCBjb2xvcj0icmVkIikKCmBgYAoKIyMgTTYKRmVhdHVyZSBzZWxlY3Rpb24gd2l0aCBsb2dpc3RpYyBnbG1uZXQKYGBge3J9CmxvZ2lzdGljX2dsbW5ldChtZXRhX002LCBleHByLCBzaWduYXR1cmVfaHVtYW4pCmBgYApMb2dpc3RpYyByZWdyZXNzaW9uIHdpdGggZnVsbCBzaWduYXR1cmUKYGBge3J9CiMgQW5hbHlzaXMgd2l0aCBnbG0KYWNjdXJhY3kgPC0gbG9naXN0aWNfZ2xtKG1ldGFfTTYsIGV4cHIsIHNpZ25hdHVyZV9odW1hbiwgVCwgVCwgVCkKYGBgCkxvZ2lzdGljIHJlZ3Jlc3Npb24gd2l0aCByYW5kb20gc2lnbmF0dXJlcwpgYGB7cn0KCnJhbmRvbV9zaWduYXR1cmVzIDwtIGxhcHBseSgxOjEwMDAsZnVuY3Rpb24oaSl7CiAgc2FtcGxlKHVuaXF1ZV9vcnRob2xvZ19tYXBwaW5nJG9ydGhvbG9nX25hbWUsbGVuZ3RoKHNpZ25hdHVyZV9odW1hbikpCn0pCgpyYW5kb21fYWNjdXJhY3kgPC0gc2FwcGx5KHJhbmRvbV9zaWduYXR1cmVzLCBmdW5jdGlvbihyYW5kb21fc2lnbmF0dXJlKXsKICBsb2dpc3RpY19nbG0obWV0YV9NNiwgZXhwciwgcmFuZG9tX3NpZ25hdHVyZSkKfSkKCmdncGxvdChkYXRhLmZyYW1lKHJhbmRvbV9hY2N1cmFjeT1yYW5kb21fYWNjdXJhY3kpLCBhZXMoeD1yYW5kb21fYWNjdXJhY3kpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucz0zMCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hY2N1cmFjeSwgY29sb3I9InJlZCIpCgpgYGAKIyMgTUkKRmVhdHVyZSBzZWxlY3Rpb24gd2l0aCBsb2dpc3RpYyBnbG1uZXQKYGBge3J9CmxvZ2lzdGljX2dsbW5ldChtZXRhX01JLCBleHByLCBzaWduYXR1cmVfaHVtYW4pCmBgYApMb2dpc3RpYyByZWdyZXNzaW9uIHdpdGggZnVsbCBzaWduYXR1cmUKYGBge3J9CiMgQW5hbHlzaXMgd2l0aCBnbG0KYWNjdXJhY3kgPC0gbG9naXN0aWNfZ2xtKG1ldGFfTUksIGV4cHIsIHNpZ25hdHVyZV9odW1hbiwgVCwgVCwgVCkKYGBgCkxvZ2lzdGljIHJlZ3Jlc3Npb24gd2l0aCByYW5kb20gc2lnbmF0dXJlcwpgYGB7cn0KCnJhbmRvbV9zaWduYXR1cmVzIDwtIGxhcHBseSgxOjEwMDAsZnVuY3Rpb24oaSl7CiAgc2FtcGxlKHVuaXF1ZV9vcnRob2xvZ19tYXBwaW5nJG9ydGhvbG9nX25hbWUsbGVuZ3RoKHNpZ25hdHVyZV9odW1hbikpCn0pCgpyYW5kb21fYWNjdXJhY3kgPC0gc2FwcGx5KHJhbmRvbV9zaWduYXR1cmVzLCBmdW5jdGlvbihyYW5kb21fc2lnbmF0dXJlKXsKICBsb2dpc3RpY19nbG0obWV0YV9NSSwgZXhwciwgcmFuZG9tX3NpZ25hdHVyZSkKfSkKCmdncGxvdChkYXRhLmZyYW1lKHJhbmRvbV9hY2N1cmFjeT1yYW5kb21fYWNjdXJhY3kpLCBhZXMoeD1yYW5kb21fYWNjdXJhY3kpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucz0zMCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hY2N1cmFjeSwgY29sb3I9InJlZCIpCgpgYGA=